package com.woniu.park.config;

/**
 * @author 张洛诚
 * @date 2019/8/29 21:09:34
 * @description
 */


import com.alibaba.fastjson.JSONObject;
import com.woniu.park.log.SystemControllerLog;
import com.woniu.park.pojo.ActiveUser;
import com.woniu.park.pojo.LogModel;
import com.woniu.park.util.IpUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
@Aspect
@Slf4j //使用log，不用再定义
public class WebLogAspect {

    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    private final ThreadLocal<String> methodDescribe = new ThreadLocal<>(); //

    private static final ThreadLocal<LogModel> logThreadLocal = new NamedThreadLocal<LogModel>("ThreadLocal log");

    private static final ThreadLocal<ActiveUser> currentUser = new NamedThreadLocal<>("ThreadLocal user");

    private static final Integer AVAILBLE=1;

    @Autowired
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private FanoutExchange exchange;

    @Pointcut("@annotation( com.woniu.park.log.SystemControllerLog)") //元注解类的权限名
    public void controllerAspect() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        log.info("进入日志切面前置通知!!");
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        startTime.set(System.currentTimeMillis());//线程绑定变量（该数据只有当前请求的线程可见）
        Date beginTime = new Date();
        if (log.isErrorEnabled()) {//这里日志级别为error
            log.debug("开始计时: {}  URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                    .format(beginTime), request.getRequestURI());
        }
        //读取session中的用户
        HttpSession session = request.getSession();
        ActiveUser user = (ActiveUser) session.getAttribute("activeUser");
        currentUser.set(user);
    }

    /**
     * 后处理
     *
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "controllerAspect()")
    public void doAfterReturning(JoinPoint joinPoint,Object ret) throws Throwable {
        ActiveUser user = currentUser.get(); //获取线程的user
        // 处理完请求，返回内容
        String level = "info"; //错误级别
        long endTime = System.currentTimeMillis();  //2、结束时间
        Date dateEnd = new Date(endTime);
        long excTime = System.currentTimeMillis() - startTime.get();
        LogModel logModel = bulidLogModel(joinPoint,user);
        logModel.setLevel(level);
        logModel.setException("无异常"); //异常信息
        logModel.setResponseParams(JSONObject.toJSONString(ret)); //响应信息
        logModel.setEndTime(dateEnd);
        logModel.setExcTime(BigInteger.valueOf(endTime));
        //放入消息队列，其他服务调取，存储
        sendLogModelToQueue(logModel);
        log.info(methodDescribe.get() + "返回参数 : {}", JSONObject.toJSONString(ret));
        log.info("请求执行时间 : " + (System.currentTimeMillis() - startTime.get()) + " ms"); //执行时间
    }


    /**
     * 将日志信息丢送到消息队列
     * @param logModel
     */
    private void sendLogModelToQueue(LogModel logModel) {
        CorrelationData correlationData=new CorrelationData();
        //System.out.println(logModel);
        rabbitTemplate.convertAndSend(exchange.getName(), null,logModel,correlationData);
    }
    /**
     * 异常通知 记录操作报错日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        log.info("进入日志切面后置通知!!");
        ActiveUser user = currentUser.get();
        String level = "error";                       //日志类型
        long endTime = System.currentTimeMillis();  //2、结束时间
        Date dateEnd = new Date(endTime);
        long excTime = System.currentTimeMillis() - startTime.get(); //执行时间
        LogModel logModel = bulidLogModel(joinPoint,user);
        logModel.setLevel(level); //日志级别
        logModel.setException(e.getMessage()); //异常
        logModel.setEndTime(dateEnd);  //结束时间
        logModel.setExcTime(BigInteger.valueOf(excTime)); //执行时间
        //放入消息队列，其他服务调取，存储
        sendLogModelToQueue(logModel);
    }

    /**
     * 构建logmodel
     * @param joinPoint
     * @param user
     * @return
     */
    private LogModel bulidLogModel(JoinPoint joinPoint, ActiveUser user){
        String title = "";
        try {
            title = getControllerMethodDescription(joinPoint); //获取注解方法的标题
        } catch (Exception e) {
            e.printStackTrace();
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        String remoteAddr = IpUtils.getRemoteAddr(request);//请求的IP
        String requestUri = request.getRequestURI();//请求的Uri
        String method = joinPoint.getSignature().getName();//请求的方法名
        String clazz = joinPoint.getSignature().getDeclaringTypeName(); //类名
        long beginTime = startTime.get();//得到线程绑定的局部变量（开始时间）
        Date dateBegin = new Date(beginTime);
        LogModel logModel=new LogModel();
        logModel.setTitle(title); //标题
        logModel.setRequestUri(requestUri); //请求地址
        logModel.setRemoteAddr(remoteAddr);  //ip地址
        logModel.setRequestParams(parseArgs(joinPoint)); //请求参数
        logModel.setStartTime(dateBegin); //开始时间
        logModel.setClazz(clazz);  //类名
        logModel.setMethod(method); //方法名
        if(user!=null){
            logModel.setUserId(user.getUserId()); //用户名
            logModel.setTypeId(user.getTypeId()); //来源session
        }else{
            logModel.setUserId(null);
            logModel.setTypeId(5);//未知用户
        }
        logModel.setAvailable(AVAILBLE);//默认不删除
        return logModel;
    }

    /**
     * 获取requestcontext
     *
     * @return
     */
    public HttpServletRequest getRequestContext() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(SystemControllerLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

    /**
     * 输出切点日志
     *
     * @param joinPoint
     * @param request
     */
    private void recordMethodLog(JoinPoint joinPoint, HttpServletRequest request) {
        ActiveUser user = currentUser.get();
        Method method = getMethod(joinPoint);
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        String value = "";
        if (null != apiOperation) {
            value = apiOperation.value();
            if (StringUtils.isNotEmpty(value)) {
                methodDescribe.set(value);
            }
        }
        log.info("ip地址" + IpUtils.getRemoteAddr(request));
        log.info("请求地址 : " + request.getRequestURL().toString());
        log.info("请求方法 : " + request.getMethod());
        log.info("访问者IP : " + request.getRemoteAddr());
        log.info("用户ID" + user.getUserId());
        log.info("类名 : " + joinPoint.getSignature().getDeclaringTypeName() + "方法名 : "
                + joinPoint.getSignature().getName());
        log.info(value + "传入参：{} ", parseArgs(joinPoint));
    }

    /**
     * 获取切点的方法
     *
     * @param joinPoint
     * @return
     */
    private Method getMethod(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        return methodSignature.getMethod();
    }

    /**
     * 获取切点的输入参数
     *
     * @param joinPoint
     * @return
     */
    private String parseArgs(JoinPoint joinPoint) {
        StringBuffer sb = new StringBuffer();
        Object[] args = joinPoint.getArgs();
        System.out.println(args.toString());
        if (args!=null && args.length>0) {
            sb.append(JSONObject.toJSONString(args[0]));
        }
        return sb.toString();
    }
}
